package demo.timewheel;

import cn.hutool.core.lang.Assert;

import java.util.Objects;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.stream.Stream;

/**
 * 计时器轮
 * 创建人: zhonghaijun
 * 创建时间: 2023-08-08 16:49:46
 */
public class TimerWheel implements Timer {

    /** 线程池 */
    private final ExecutorService executorService;

    /** 任务队列 */
    private final BlockingQueue<TimerTaskWrapper> taskQueue;

    /** 每一个bucket时间间隔，默认为1毫秒 */
    private final long duration;

    /** 开始时间 */
    private volatile long startTime;

    /** 时间轮线程 */
    private Thread timeWheelThread;

    /** 只能一个线程 */
    private final CountDownLatch countDownLatch;

    /**
     * 时间轮每一个间隔的桶数组，根据时间论的理论，每一个时间间隔都是一个桶，例如：1秒中分为8个桶，那么每一个桶的时间间隔就是1/8秒（125毫秒）
     * 这里根据外面传入的参数，创建一个桶数组，数组的大小就是每一个时间间隔的个数
     */
    private final Bucket[] buckets;

    /** 桶数量 */
    private final int bucketNum;

    /** 是否已经启动 */
    private volatile boolean started = false;

    /**
     * 时间轮转动的时间
     *
     * @param duration   持续时间
     * @param timeUnit   时间单位
     * @param bucketSize 桶大小
     * @return {@code  }
     * @author zhonghaijun
     * @date 2023-08-08 16:58:34
     */
    public TimerWheel(long duration, TimeUnit timeUnit, int bucketSize) {
        long nanos = timeUnit.toNanos(duration);
        //设置时间间隔，先将时间转换为纳秒，判断传入的时间时候小于1毫秒
        this.duration = TimeUnit.MILLISECONDS.toNanos(1) > nanos ? 1 : timeUnit.toMillis(duration);
        this.timeWheelThread = new Thread(new Worker());
        this.taskQueue = new ArrayBlockingQueue<>(bucketSize);
        //初始化桶的数量，默认128个，如果间隔时间是1秒，那么就是128秒为一圈
        this.bucketNum = bucketSize < 1 ? 128 : bucketSize;
        this.buckets = new Bucket[this.bucketNum];
        //初始化桶
        Stream.iterate(0, i -> ++i).limit(bucketSize).forEach(i -> this.buckets[i] = new Bucket());
        int availableProcessors = Runtime.getRuntime().availableProcessors();
        //创建一个固定线程池
        this.executorService = new ThreadPoolExecutor(availableProcessors,
                                                      availableProcessors,
                                                      0L,
                                                      TimeUnit.MILLISECONDS,
                                                      new LinkedBlockingQueue<>());
        this.countDownLatch = new CountDownLatch(1);
    }

    /**
     * 创建任务
     *
     * @param timerTask 计时器任务
     * @param delay     延迟
     * @param timeUnit  时间单位
     * @author zhonghaijun
     * @date 2023-08-08 17:53:33
     */
    @Override
    public void createTimerTask(TimerTask timerTask, long delay, TimeUnit timeUnit) {
        this.start();
        //计算任务需要结束的时间
        long deadline = System.currentTimeMillis() + timeUnit.toMillis(delay) - startTime;
        //提到到任务队列中
        this.taskQueue.offer(new TimerTaskWrapper(timerTask, deadline));
    }

    /**
     * 启动时间轮
     *
     * @author zhonghaijun
     * @date 2023-08-08 17:08:53
     */
    private void start() {
        if (this.started) {
            return;
        }
        started = true;
        //启动时间轮的任务
        timeWheelThread.start();
        try {
            //等待时间轮任务的启动
            this.countDownLatch.await();
            System.out.println("时间轮启动完成");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 时间轮的工作线程
     * 创建人: zhonghaijun
     * 创建时间: 2023-08-08 17:10:21
     */
    class Worker implements Runnable {
        //记录tick的次数
        private long tick;
        @Override
        public void run() {
            //初始化开始时间
            TimerWheel.this.startTime = System.currentTimeMillis();
            TimerWheel.this.countDownLatch.countDown();
            while (true) {
                //获取到下一次执行的时间
                long nextTick = nextTick();
                if (nextTick <= 0) {
                    continue;
                }
                /**
                 * 根据当前时间戳跟桶的数量进行取膜来确定指定时间间隔的桶的索引
                 * 例如：当前桶的数量为128，那么取膜的结果就是0-127，那么就可以确定当前时间戳的任务应该放到哪一个桶中
                 * tick代表转动的次数，如果tick为128，那么超过了桶的数量，证明就到了第二圈执行
                 */
                int bucketIndex = (int) tick & (TimerWheel.this.bucketNum - 1);
                Bucket bucket = TimerWheel.this.buckets[bucketIndex];
                //处理阻塞队列中的任务，将阻塞队列里面的数据存储到桶中
                doHandleQueueTask();
                //处理过期的数据
                bucket.expire(nextTick);
                tick++;
            }
        }

        /**
         * 处理阻塞队列中的任务
         *
         * @author zhonghaijun
         * @date 2023-08-08 17:33:39
         */
        private void doHandleQueueTask() {
            for (int i = 0; i < 1024; i++) {
                //从阻塞队列中拿到任务
                TimerTaskWrapper task = TimerWheel.this.taskQueue.poll();
                //队列为空的，直接退出
                if (Objects.isNull(task)) { break; }
                /**
                 * 计算任务的时间轮需要转动的次数，例如：任务A需要在第8毫秒的时候执行，而当前时间轮的间隔是1毫秒，那么我需要转动8次
                 */
                long taskTicks = task.deadline / TimerWheel.this.duration;
                /**
                 * 计算转动的圈数：
                 * 因为如果时间截至超过了 128个桶，时间间隔为1毫秒，那么1圈只会执行128毫秒，如果任务的时间戳是300毫秒，如果这是tick为1，那么就需要转动 300 - 1 = 2圈
                 * 如果现在 tick已经转了 128次，那么就需要转动 300 - 128 = 172次，所以需要转动的圈数就是 172 / 128 = 1圈
                 */
                task.rounds = (taskTicks - tick) / TimerWheel.this.bucketNum;
                long ticks = Math.max(taskTicks, tick);
                //计算任务所在的桶的索引
                int bucketIndex = (int) ticks & (TimerWheel.this.bucketNum - 1);
                //将任务存放到桶里面
                Bucket bucket = buckets[bucketIndex];
                bucket.addTimeTask(task);
            }
        }

        /**
         * 线程一直计算下一次结束的时间
         *
         * @return long
         * @author zhonghaijun
         * @date 2023-08-08 17:17:30
         */
        private long nextTick() {
            //计算下一次结束的时间，默认间隔1毫秒，一开始就是 1 * (0 + 1) = 1毫秒，下一次就是 1 * (1 + 1) = 2毫秒
            long deadline = TimerWheel.this.duration * (tick + 1);
            while (true) {
                //获取当前时间减去启动的时间，缩小时间范围
                long current = System.currentTimeMillis() - startTime;
                //获取当前桶的截至时间间隔减去当前时间，每一个桶都是需要等到这个桶的截至时间才能执行里面的任务
                long sleepTime = deadline - current;
                //判断当前时间是否大于等于下一次结束的时间
                if (sleepTime <= 0) {
                    //返回当前时间，到了任务可以执行了
                    return current;
                }
                try {
                    //休眠到当前刻度还剩下的时间，例如：当前时间是 1毫秒，下一次结束的时间是 2毫秒，那么就休眠 1毫秒
                    Thread.sleep(sleepTime);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    class Bucket {
        TimerTaskWrapper head;
        TimerTaskWrapper tail;

        /**
         * 添加时间任务
         *
         * @param task 任务
         * @author zhonghaijun
         * @date 2023-08-08 17:42:14
         */
        public void addTimeTask(TimerTaskWrapper task) {
            if (task == null) {
                return;
            }
            if (head == null) {
                tail = task;
                head = tail;
            } else {
                tail.next = task;
                task.prev = tail;
                tail = task;
            }
        }

        /**
         * 移除时间任务
         *
         * @param task 任务
         * @return {@code TimerTaskWrapper }
         * @author zhonghaijun
         * @date 2023-08-08 17:50:57
         */
        public TimerTaskWrapper removeTimerTask(TimerTaskWrapper task) {
            TimerTaskWrapper next = task.next;
            //判断的前置任务是否为空，如果为空就把当前任务的后置任务设置到前置任务中
            if (task.prev != null) {
                task.prev.next = next;
            }
            //判断后置任务是否为空，如果为空就把当前任务的后置任务的前置任务设置为当前任务的前置任务
            if (task.next != null) {
                task.next.prev = task.prev;
            }
            //如果当前任务是头部任务
            if (task == head) {
                //如果当前任务首尾都一样，那么直接置为空
                if (task == tail) {
                    head = null;
                    tail = null;
                } else {
                    //直接将头部任务设置为下一个任务
                    head = next;
                }
            } else if(task == tail) {
                task = tail.prev;
            }
            task.prev = null;
            task.next = null;
            return next;
        }

        /**
         * 遍历链表中到期的数据
         *
         * @param deadline 最后期限
         * @author zhonghaijun
         * @date 2023-08-08 17:43:11
         */
        public void expire(long deadline) {
            TimerTaskWrapper taskWrapper = head;
            //遍历桶中的数据
            while (taskWrapper != null) {
                //获取到下一个任务
                TimerTaskWrapper next = taskWrapper.next;
                //判断头部数据的圈数是否小于等于0，如果圈数并不小于等于0，那么证明时间轮还没有转到这个一圈
                if (taskWrapper.rounds <= 0) {
                    //判断头部任务的截至时间是否小于等于最后期限
                    next = removeTimerTask(taskWrapper);
                    //判断任务的截至时间是否小于传入的截至时间
                    if (taskWrapper.deadline <= deadline) {
                        //执行任务
                        taskWrapper.expire();
                    }
                } else {
                    //减少时间轮的圈数
                    taskWrapper.rounds--;
                }
                taskWrapper = next;
            }
        }
    }

    class TimerTaskWrapper implements Runnable {
        /** 任务 */
        private TimerTask task;
        /** 任务执行的截至时间 */
        private long deadline;
        /** 需要多少圈 */
        private long rounds;
        /** 上一个 */
        TimerTaskWrapper prev;
        /** 下一个 */
        TimerTaskWrapper next;

        public TimerTaskWrapper(TimerTask task, long deadline) {
            Assert.notNull(task, "任务不能为空");
            this.task = task;
            this.deadline = deadline;
        }

        @Override
        public void run() {
            this.task.run();
        }

        /**
         * 任务时间到期放到线程池中执行
         *
         * @author zhonghaijun
         * @date 2023-08-08 17:41:49
         */
        public void expire() {
            TimerWheel.this.executorService.execute(this);
        }
    }
}
